package org.chronologger.poppaea.server;

import java.util.Collection;
import java.util.HashMap;

import javax.jdo.PersistenceManager;
import javax.jdo.Query;
import javax.jdo.Transaction;

import org.chronologger.poppaea.client.domain.ChronologyData;
import org.chronologger.poppaea.client.domain.ChronologyService;
import org.chronologger.poppaea.client.domain.Location;
import org.chronologger.poppaea.client.domain.NotSupportedOnClientException;
import org.chronologger.poppaea.client.domain.Person;
import org.chronologger.poppaea.client.domain.Scene;
import org.chronologger.poppaea.server.domain.PersistentScene;

import com.google.gwt.user.server.rpc.RemoteServiceServlet;

public class ChronologyServiceImpl extends RemoteServiceServlet implements
		ChronologyService {

	private static final long serialVersionUID = 1L;

	@SuppressWarnings("unchecked")
	public <T> boolean deleteOne(PersistenceManager pm, Class<T> klass,
			String query) {
		Query q = pm.newQuery(klass, query);
		Collection<T> result = (Collection<T>) q.execute();
		if (result.size() == 1) {
			pm.deletePersistent(result.iterator().next());
			return true;
		} else
			return false;
	}

	public <T> boolean deleteOne(Class<T> klass, String query) {
		PersistenceManager pm = PMF.get().getPersistenceManager();
		try {
			return deleteOne(pm, klass, query);
		} finally {
			pm.close();
		}
	}

	public <T> boolean deleteById(Class<T> klass, Long id) {
		return deleteOne(klass, "id == " + id);
	}

	public <T> T getById(Class<T> klass, Long id) {
		PersistenceManager pm = PMF.get().getPersistenceManager();
		try {
			return pm.getObjectById(klass, id);
		} finally {
			pm.close();
		}
	}

	private <T> T persist(T t) {
		PersistenceManager pm = PMF.get().getPersistenceManager();
		try {
			pm.makePersistent(t);
		} finally {
			pm.close();
		}
		return t;
	}

	public Location addLocation(Long chronologyKey) {
		return persist(new Location(chronologyKey));
	}

	public void addPerson(Long sceneKey, Long characterKey) {
		SceneCharacterMapping newMapping = new SceneCharacterMapping();
		newMapping.characterId = characterKey;
		newMapping.sceneId = sceneKey;
		persist(newMapping);
	}

	public Scene addScene(Long locationKey) {
		PersistentScene pscene = persist(new PersistentScene(locationKey));
		Scene newScene;
		try {
			newScene = new Scene(pscene.getId(), locationKey);
			return newScene;
		} catch (NotSupportedOnClientException e) {
			// cannot happen on server
			return null;
		}
	}

	public ChronologyData createChronology(String name) {
		ChronologyData newChronology = new ChronologyData();
		newChronology.setName(name);
		return persist(newChronology);
	}

	public Person createPerson(Long chronologyKey) {
		Person newPerson = new Person("");
		newPerson.setChronologyId(chronologyKey);
		return persist(newPerson);
	}

	public void deleteChronology(Long chronologyKey) {
		deleteById(ChronologyData.class, chronologyKey);
	}

	public void deletePerson(Long personKey) {
		deleteById(Person.class, personKey);
	}

	@SuppressWarnings("unchecked")
	public ChronologyData getChronology(Long chronologyKey) {
		PersistenceManager pm = PMF.get().getPersistenceManager();
		try {
			Query q = pm.newQuery(ChronologyData.class, "id == "
					+ chronologyKey);
			Collection<ChronologyData> result = (Collection<ChronologyData>) q
					.execute();
			if (result.size() == 1) {
				ChronologyData cdata = result.iterator().next();
				populateChronology(cdata);
				return cdata;
			} else
				return null;
		} finally {
			pm.close();
		}
	}

	@SuppressWarnings("unchecked")
	protected void populateChronology(ChronologyData cdata) {
		PersistenceManager pm = PMF.get().getPersistenceManager();
		int sceneCount = 0;
		try {
			// load characters
			HashMap<Long, Person> peopleMapping = new HashMap<Long, Person>();
			Query q = pm.newQuery(Person.class, "chronologyId == "
					+ cdata.getId());
			Collection<Person> people = (Collection<Person>) q.execute();
			for (Person person : people) {
				cdata.characters.add(person);
				peopleMapping.put(person.getId(), person);
			}

			// load locations
			q = pm.newQuery(Location.class, "chronologyId == " + cdata.getId());
			Collection<Location> locations = (Collection<Location>) q.execute();
			for (Location location : locations) {
				cdata.locations.add(location);
				// load scenes
				Query qs = pm.newQuery(PersistentScene.class, "locationId == "
						+ location.getId());
				Collection<PersistentScene> scenes = (Collection<PersistentScene>) qs
						.execute();
				for (PersistentScene pscene : scenes) {
					sceneCount++;
					Scene scene = pscene.toScene(location);
					location.getScenes().add(scene);
					// load character mapping
					Query qp = pm.newQuery(SceneCharacterMapping.class,
							"sceneId == " + pscene.getId());
					Collection<SceneCharacterMapping> mapping = (Collection<SceneCharacterMapping>) qp
							.execute();

					for (SceneCharacterMapping map : mapping) {
						scene.addPerson(peopleMapping.get(map.characterId));
					}
				}
			}
		} finally {
			pm.close();
			System.out.printf("%d scenes loaded\n", sceneCount);
		}
	}

	@SuppressWarnings("unchecked")
	public void removeLocation(Long locationKey) {
		PersistenceManager pm = PMF.get().getPersistenceManager();
		try {
			Query lq = pm.newQuery(Location.class, "id == " + locationKey);
			Collection<Location> result = (Collection<Location>) lq.execute();
			if (result.size() == 1) {
				Location location = result.iterator().next();
				pm.deletePersistent(location);
			}

			Query sq = pm.newQuery(PersistentScene.class, "locationId == "
					+ locationKey);
			Collection<PersistentScene> scenes = (Collection<PersistentScene>) sq
					.execute();
			for (PersistentScene p : scenes) {
				removeScene(pm, p);
			}
			pm.newQuery();
		} finally {
			pm.close();
		}
	}

	public void removePerson(Long sceneKey, Long characterKey) {
		deleteOne(SceneCharacterMapping.class, "sceneId == " + sceneKey
				+ " AND characterId == " + characterKey);
	}

	@SuppressWarnings("unchecked")
	private void removeScene(PersistenceManager pm, PersistentScene scene) {
		pm.deletePersistent(scene);

		// delete characterMapping
		Query mapping = pm.newQuery(SceneCharacterMapping.class, "sceneId == "
				+ scene.getId());
		Collection<SceneCharacterMapping> scmap = (Collection<SceneCharacterMapping>) mapping
				.execute();

		for (SceneCharacterMapping sc : scmap) {
			pm.deletePersistent(sc);
		}
	}

	public void removeScene(Long sceneKey) {
		PersistenceManager pm = PMF.get().getPersistenceManager();
		try {
			removeScene(pm, pm.getObjectById(PersistentScene.class, sceneKey));
		} finally {
			pm.close();
		}
	}

	public void updateLocation(Location location) {
		PersistenceManager pm = PMF.get().getPersistenceManager();
		try {	
			Transaction tx = pm.currentTransaction();
			tx.begin();
			System.out.printf("Updating location. color: %s\n", location.getColor());
			pm.makePersistent(location);
			tx.commit();
		} finally {
			pm.close();
		}
	}

	public void updatePerson(Person person) {
	}

	public void updateScene(Scene scene) {
		PersistenceManager pm = PMF.get().getPersistenceManager();
		try {	
			Transaction tx = pm.currentTransaction();
			tx.begin();
			PersistentScene pscene = pm.getObjectById(PersistentScene.class, scene.getId());
			pscene.fromScene(scene);			
			pm.makePersistent(pscene);
			tx.commit();
		} finally {
			pm.close();
		}
	}

}
